/*-------------------------------------------------------------------------
 *
 * fe-secure-common.c
 *
 * 公共实现无关的 SSL 支持代码
 *
 * 虽然 fe-secure.c 包含 libpq 其他部分调用的接口，但此文件包含由库特定的实现（如 fe-secure-openssl.c）使用的支持例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-secure-common.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <arpa/inet.h>

#include "fe-secure-common.h"

#include "libpq-int.h"
#include "pqexpbuffer.h"

/*
 * 检查通配符证书是否与服务器主机名匹配。
 *
 * 规则如下：
 *	1. 我们仅将 '*' 字符视为通配符
 *	2. 我们仅在字符串开头匹配通配符
 *	3. '*' 字符不匹配 '.'，这意味着我们仅匹配单个路径组件。
 *	4. 我们不支持在单个模式中使用多个 '*'。
 *
 * 这大致符合 RFC2818，但与大多数浏览器的实现相反（第3点是差异所在）
 *
 * 匹配始终不区分大小写，因为 DNS 不区分大小写。
 */
static bool fc_wildcard_certificate_match(const char *fc_pattern, const char *fc_string)
{
	int			fc_lenpat = strlen(fc_pattern);
	int			fc_lenstr = strlen(fc_string);

	/* 如果不以通配符开头，则不匹配（规则 1 和 2） */
	if (fc_lenpat < 3 ||
		fc_pattern[0] != '*' ||
		fc_pattern[1] != '.')
		return false;

	/* 如果模式长于字符串，则永远无法匹配 */
	if (fc_lenpat > fc_lenstr)
		return false;

	/*
	 * 如果字符串未以模式结尾（减去通配符），则不匹配
	 */
	if (pg_strcasecmp(fc_pattern + 1, fc_string + fc_lenstr - fc_lenpat + 1) != 0)
		return false;

	/*
	 * 如果模式开始匹配的地方左侧有一个点，则不匹配（规则 3）
	 */
	if (strchr(fc_string, '.') < fc_string + fc_lenstr - fc_lenpat)
		return false;

	/* 字符串以模式结尾，并且之前没有点，因此我们匹配 */
	return true;
}

/*
 * 检查服务器证书中的名称是否与对等方的主机名匹配。
 *
 * 如果名称匹配，则返回 1；如果不匹配，则返回 0。在错误情况下，返回
 * -1，并设置 libpq 错误消息。
 *
 * 从证书中提取的名称在 *store_name 中返回。调用者负责释放它。
 */
int pq_verify_peer_name_matches_certificate_name(PGconn *fc_conn,
											 const char *fc_namedata, size_t fc_namelen,
											 char **fc_store_name)
{
	char	   *fc_name;
	int			fc_result;
	char	   *fc_host = fc_conn->connhost[fc_conn->whichhost].host;

	*fc_store_name = NULL;

	if (!(fc_host && fc_host[0] != '\0'))
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("host name must be specified\n"));
		return -1;
	}

	/*
	 * 没有保证证书返回的字符串是以 NULL 结尾的，因此制作一个
	 * 以 NULL 结尾的副本。
	 */
	fc_name = malloc(fc_namelen + 1);
	if (fc_name == NULL)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("out of memory\n"));
		return -1;
	}
	memcpy(fc_name, fc_namedata, fc_namelen);
	fc_name[fc_namelen] = '\0';

	/*
	 * 拒绝证书的常见或备用名称中嵌入的 NULL，以
	 * 防止类似 CVE-2009-4034 的攻击。
	 */
	if (fc_namelen != strlen(fc_name))
	{
		free(fc_name);
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("SSL certificate's name contains embedded null\n"));
		return -1;
	}

	if (pg_strcasecmp(fc_name, fc_host) == 0)
	{
		/* 精确名称匹配 */
		fc_result = 1;
	}
	else if (fc_wildcard_certificate_match(fc_name, fc_host))
	{
		/* 匹配的通配符名称 */
		fc_result = 1;
	}
	else
	{
		fc_result = 0;
	}

	*fc_store_name = fc_name;
	return fc_result;
}

/*
 * 检查服务器证书中的 IP 地址是否与对等方的
 * 主机名匹配（主机名本身必须是一个 IPv4/6 地址）。
 *
 * 如果地址匹配，则返回 1；如果不匹配，则返回 0。在错误情况下，返回
 * -1，并设置 libpq 错误消息。
 *
 * 证书的 IP 地址的字符串表示形式在 *store_name 中返回。调用者负责释放它。
 */
int pq_verify_peer_name_matches_certificate_ip(PGconn *fc_conn,
										   const unsigned char *fc_ipdata,
										   size_t fc_iplen,
										   char **fc_store_name)
{
	char	   *fc_addrstr;
	int			fc_match = 0;
	char	   *fc_host = fc_conn->connhost[fc_conn->whichhost].host;
	int			fc_family;
	char		fc_tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"];
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];

	*fc_store_name = NULL;

	if (!(fc_host && fc_host[0] != '\0'))
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("host name must be specified\n"));
		return -1;
	}

	/*
	 * 从证书中获取的数据是网络字节顺序。将我们的
	 * 主机字符串也转换为网络顺序字节，以便进行比较。（主机
	 * 字符串并不能保证实际上是一个 IP 地址，因此如果此
	 * 转换失败，我们需要将其视为不匹配而不是错误。）
	 */
	if (fc_iplen == 4)
	{
		/* IPv4 */
		struct in_addr fc_addr;

		fc_family = AF_INET;

		/*
		 * 特意使用 inet_aton()；我们接受由 inet_aton() 接受的
		 * 替代 IPv4 地址表示法，但不接受 inet_pton() 作为服务器地址。
		 */
		if (inet_aton(fc_host, &fc_addr))
		{
			if (memcmp(fc_ipdata, &fc_addr.s_addr, fc_iplen) == 0)
				fc_match = 1;
		}
	}

	/*
	 * 如果他们没有 inet_pton()，则跳过此项。然后，证书中的 IPv6 地址将
	 * 导致错误。
	 */
#ifdef HAVE_INET_PTON
	else if (fc_iplen == 16)
	{
		/* IPv6 */
		struct in6_addr fc_addr;

		fc_family = AF_INET6;

		if (inet_pton(AF_INET6, fc_host, &fc_addr) == 1)
		{
			if (memcmp(fc_ipdata, &fc_addr.s6_addr, fc_iplen) == 0)
				fc_match = 1;
		}
	}
#endif
	else
	{
		/*
		 * 既不是 IPv4 也不是 IPv6。我们可以忽略该字段，但考虑到主题
		 * ，宽容似乎是错误的。
		 */
		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("certificate contains IP address with invalid length %lu\n"),
						  (unsigned long) fc_iplen);
		return -1;
	}

	/* 生成证书 IP 的可读表示 */
	fc_addrstr = pg_inet_net_ntop(fc_family, fc_ipdata, 8 * fc_iplen, fc_tmp, sizeof(fc_tmp));
	if (!fc_addrstr)
	{
		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("could not convert certificate's IP address to string: %s\n"),
						  strerror_r(errno, fc_sebuf, sizeof(fc_sebuf)));
		return -1;
	}

	*fc_store_name = strdup(fc_addrstr);
	return fc_match;
}

/* 
 * 验证服务器证书是否与我们连接的主机名匹配。
 *
 * 考虑证书的常见名称和主题备用名称。
 */
bool pq_verify_peer_name_matches_certificate(PGconn *fc_conn)
{
	char	   *fc_host = fc_conn->connhost[fc_conn->whichhost].host;
	int			fc_rc;
	int			fc_names_examined = 0;
	char	   *fc_first_name = NULL;

	/*
	 * 如果被告知不验证对等名称，就不进行验证。返回true
	 * 表示验证成功。
	 */
	if (strcmp(fc_conn->sslmode, "verify-full") != 0)
		return true;

	/* 检查我们是否有主机名可供比较。 */
	if (!(fc_host && fc_host[0] != '\0'))
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("host name must be specified for a verified SSL connection\n"));
		return false;
	}

	fc_rc = pgtls_verify_peer_name_matches_certificate_guts(fc_conn, &fc_names_examined, &fc_first_name);

	if (fc_rc == 0)
	{
		/*
		 * 不匹配。从服务器证书中包含名称到错误
		 * 消息中，以帮助调试损坏的配置。如果有
		 * 多个名称，仅打印第一个以避免过长的
		 * 错误消息。
		 */
		if (fc_names_examined > 1)
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"\n",
											 "server certificate for \"%s\" (and %d other names) does not match host name \"%s\"\n",
											 fc_names_examined - 1),
							  fc_first_name, fc_names_examined - 1, fc_host);
		}
		else if (fc_names_examined == 1)
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
							  fc_first_name, fc_host);
		}
		else
		{
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("could not get server's host name from server certificate\n"));
		}
	}

	/* 清理 */
	if (fc_first_name)
		free(fc_first_name);

	return (fc_rc == 1);
}
